/*************************************************************************************
* 	 Copyright (C) 2010 by Information Systems Group, Saarland University  			*
*    http://infosys.cs.uni-saarland.de												*
* 	 																				*
* 	 This file is part of Hadoop++.												 	*
*																					*
*    Hadoop++ is free software: you can redistribute it and/or modify				*
*    it under the terms of the GNU Lesser General Public License as published by	*
*    the Free Software Foundation, either version 3 of the License, or				*
*    (at your option) any later version.											*
*																					*
*    Hadoop++ is distributed in the hope that it will be useful,					*
*    but WITHOUT ANY WARRANTY; without even the implied warranty of					*
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the					*
*    GNU Lesser General Public License for more details.							*
*																					*
*    You should have received a copy of the GNU Lesser General Public License		*
*    along with Hadoop++.  If not, see <http://www.gnu.org/licenses/>.				*
*************************************************************************************/
package unisb.cs.core.index.structure;

import java.text.ParseException;
import java.util.Date;


import unisb.cs.core.binary.utils.BinaryUtils;
import unisb.cs.data.types.DataTypes;

/**
 * This class manages the Trojan index header.
 * 
 * Following is how an index header looks like:
 *  ________________________________________________________________________________________
 * |			  |				|				   |					|					|		
 * | Index Offset | Data Offset | Number of Tuples | First Key in Split | Last key in Split |
 * |  (4 bytes)   |	 (4 bytes)  |	 (4 bytes)	   |	  (key B)       |	   (key B)		|	
 * |______________|_____________|__________________|____________________|___________________|
 * 
 * The index offset is the offset within the split where the index structure is stored.
 * The data offset is the offset within the split where the actual data is stored.
 * Number of tuples indicate the number of records contained in the split.
 * The first key in split is the value of index attribute of the first record in the split.
 * The last key in split is the value of index attribute of the last record in the split.
 *
 */
public class IndexHeader {
	/**
	 * We define five possible cases of query range containment within the split
	 */
	public final static int NOT_CONTAINED = 0;		// the split is out of the query range
	public final static int FULL_CONTAINED = 1;		// the split contains the query  range fully
	public final static int LEFT_CONTAINED = 2;		// the split contains only high key of the query range
	public final static int RIGHT_CONTAINED = 3;	// the split contains only low key of the query range
	public final static int POINT_CONTAINED = 4;	// the high and low key are the same and are contained
	public final static int SPANS = 5;				// the split is fully contained within the query range
	


	// header attributes
	int indexEndOffset;
	int dataEndOffset = 0;
	int noOfTuples;

	// first and last keys
	int firstIntKey, lastIntKey;
	float firstFloatKey, lastFloatKey;
	String firstStringKey, lastStringKey;
	Date firstDateKey, lastDateKey;

	// index attributes
	int keyType, keySize, actualKeySize;
	
	public IndexHeader(int keyType, int keySize) {
		this.keyType = keyType;
		this.keySize = keySize + CSSTree.OFFSET_BYTES;
		this.actualKeySize = keySize;
	}

	/**
	 * @return The total header size
	 */
	public int getHeaderSize() {
		return 12 + 2 * keySize;
	}

	/**
	 * This method parses the header bytes.
	 * @param header The header bytes to parse
	 */
	public void parse(byte[] header) {
		indexEndOffset = BinaryUtils.getInt(header, 0);
		dataEndOffset = BinaryUtils.getInt(header, 4);
		noOfTuples = BinaryUtils.getInt(header, 8);

		switch (keyType) {
		case DataTypes.INT_TYPE:
			firstIntKey = BinaryUtils.getInt(header, 12);
			lastIntKey = BinaryUtils.getInt(header, 12 + keySize);
			break;

		case DataTypes.FLOAT_TYPE:
			firstFloatKey = BinaryUtils.getFloat(header, 12);
			lastFloatKey = BinaryUtils.getFloat(header, 12 + keySize);
			break;

		case DataTypes.CHAR_TYPE:
			//firstStringKey = BinaryUtils.getString(header, 12, keySize);
			firstStringKey = BinaryUtils.getString(header, 12, actualKeySize).trim();
			lastStringKey = BinaryUtils.getString(header, 12 + keySize,	actualKeySize).trim();
			break;

		case DataTypes.DATE_TYPE:
			firstDateKey = BinaryUtils.getDate(header, 12, keySize);
			lastDateKey = BinaryUtils.getDate(header, 12 + keySize,
					actualKeySize);
			break;
		default:
			System.out.println("Key Type not resolved!");
		}
	}

	/**
	 * Get the split-query overlap type for integer index key
	 */
	public int getIntOverlapType(String strK1, String strK2) {
		int k1 = Integer.parseInt(strK1);
		int k2 = Integer.parseInt(strK2);
		if ((k1 >= firstIntKey) && (k2 <= lastIntKey)) {
			if (k1 == k2)
				return POINT_CONTAINED;
			else
				return FULL_CONTAINED;
		} else if ((k1 >= firstIntKey) && (k1 <= lastIntKey))
			return LEFT_CONTAINED;
		else if ((k2 >= firstIntKey) && (k2 <= lastIntKey))
			return RIGHT_CONTAINED;
		else if ((k1 < firstIntKey) && (k2 > lastIntKey))
			return SPANS;
		else
			return NOT_CONTAINED;
	}

	/**
	 * Get the split-query overlap type for float index key
	 */
	public int getFloatOverlapType(String strK1, String strK2) {
		float k1 = Float.parseFloat(strK1);
		float k2 = Float.parseFloat(strK2);
		if ((k1 >= firstFloatKey) && (k2 <= lastFloatKey)) {
			if (k1 == k2)
				return POINT_CONTAINED;
			else
				return FULL_CONTAINED;
		} else if ((k1 >= firstFloatKey) && (k1 <= lastFloatKey))
			return LEFT_CONTAINED;
		else if ((k2 >= firstFloatKey) && (k2 <= lastFloatKey))
			return RIGHT_CONTAINED;
		else if ((k1 < firstFloatKey) && (k2 > lastFloatKey))
			return SPANS;
		else
			return NOT_CONTAINED;
	}

	/**
	 * Get the split-query overlap type for char index key
	 */
	public int getCharOverlapType(String k1, String k2) {
		if ((k1.compareTo(firstStringKey) >= 0)
				&& (k2.compareTo(lastStringKey) <= 0)) {
			if (k1.equals(k2))
				return POINT_CONTAINED;
			else
				return FULL_CONTAINED;
		} else if ((k1.compareTo(firstStringKey) >= 0)
				&& (k1.compareTo(lastStringKey) <= 0))
			return LEFT_CONTAINED;
		else if ((k2.compareTo(firstStringKey) >= 0)
				&& (k2.compareTo(lastStringKey) <= 0))
			return RIGHT_CONTAINED;
		else if ((k1.compareTo(firstStringKey) < 0)
				&& (k2.compareTo(lastStringKey) > 0))
			return SPANS;
		else
			return NOT_CONTAINED;
	}

	/**
	 * Get the split-query overlap type for date index key
	 */
	public int getDateOverlapType(String strK1, String strK2) {
		try {
			Date k1 = DataTypes.dateParser.parse(strK1);
			System.out.println("Getting Date Overlap Type: Low Key="
					+ k1.toString());
			Date k2 = DataTypes.dateParser.parse(strK2);
			System.out.println("Getting Date Overlap Type: High Key="
					+ k2.toString());

			if ((k1.compareTo(firstDateKey) >= 0)
					&& (k2.compareTo(lastDateKey) <= 0)) {
				if (k1.compareTo(k2) == 0)
					return POINT_CONTAINED;
				else
					return FULL_CONTAINED;
			} else if ((k1.compareTo(firstDateKey) >= 0)
					&& (k1.compareTo(lastDateKey) <= 0))
				return LEFT_CONTAINED;
			else if ((k2.compareTo(firstDateKey) >= 0)
					&& (k2.compareTo(lastDateKey) <= 0))
				return RIGHT_CONTAINED;
			else if ((k1.compareTo(firstDateKey) < 0)
					&& (k2.compareTo(lastDateKey) > 0))
				return SPANS;
			else
				return NOT_CONTAINED;
		} catch (ParseException e) {
			return NOT_CONTAINED;
		}
	}

	/**
	 * Get the split-query overlap type
	 */
	public int getOverlapType(String key1, String key2) {
		// parse key according to type
		switch (keyType) {
		case DataTypes.INT_TYPE:
			return getIntOverlapType(key1, key2);
		case DataTypes.FLOAT_TYPE:
			return getFloatOverlapType(key1, key2);
		case DataTypes.CHAR_TYPE:
			return getCharOverlapType(key1, key2);
		case DataTypes.DATE_TYPE:
			return getDateOverlapType(key1, key2);
		default:
			return NOT_CONTAINED;
		}
	}

	/**
	 * @return The index size
	 */
	public int getIndexSize() {
		return indexEndOffset;
	}

	/**
	 * @return The data size as integer
	 */
	public int getIntDataSize() {
		return dataEndOffset;
	}

	/**
	 * @return The number of tuples in the split
	 */
	public int getNofTuples() {
		return noOfTuples;
	}
}
